-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support dynamic port for c:Endpoint.url/0
#5553
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to coordinate changes as needed on mtrudel/bandit#212 to add any necessary functions to the Bandit adapter to keep this PR simple.
https -> {"https", https[:port] || 443} | ||
http -> {"http", http[:port] || 80} | ||
https -> {"https", transport_port(endpoint, https, :https)} | ||
http -> {"http", transport_port(endpoint, http, :http)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to be so explicit about sorting through the config ourselves? Rather than special casing the times where the port is dynamic, we could just always ask the adapter for the port (I'm happy to add dynamic_port/2
to Bandit.PhoenixAdapter
) and be done with it. Taken with the changes suggested for domain sockets above, it would strip away most of the complexity of the changes to this file, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good, but it also kinda prepends on the adapter being able to return a port / being started. That should be a simpler check though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point - at many of the calls we're guaranteed that the server won't be running. What ought to be the behaviour in those cases, do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if the server is not running the current behaviour (of rendering :0
as port) is already the best one. Phoenix builds up a url it expects the server to bind to. That behaviour just doesn't help when a server is actually started and urls need to be build to connect to that server. In that case phoenix should be able to provide the port the server was bound to instead.
This means querying the information after startup and at best also make sure that cached information stays up to date with the bound port in case of crashes/restarts or the suspend you mentioned.
case :ranch.get_addr(build_ref(endpoint, scheme)) do | ||
{:local, _unix_path} -> raise "Trying to determine dynamic port, but the adapter is configured to use unix sockets. Remove the configuration for `[port: 0]` on the #{scheme} scheme." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest plumbing through a value of nil
for the port in the case of domain sockets; it's better than falling back to the scheme port as we're doing below.
Regardless, it's an unwinnable battle either way I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I saw it the other way round. If you're using unix socket nothing should even ask for a port, as that doesn't really make sense. Given the above discussion returning nil
might be more appropriate though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. If we step back and observe that the goal here is to build a URI for the server, we should be asking 'what does that URI even look like for a domain socket' (and secondarily, 'is that the URI that the user cares about').
We have answers to this already in Bandit and Cowboy. If we wanted to follow down that path we should be extracting both the address and the port from the underlying server (that makes sense to me, FWIW).
Crashes of specific handlers won't unwind all of Bandit or Cowboy, so we're fine there. What may happen though, is changes to the port number in the case of suspension (see Thousand Island docs and Ranch docs, 'Suspending and resuming a listener'). |
Yeah, I don't think we should do this, especially if url will change between compile vs runtime. We use this in Livebook and we basically compute and store our own URL based on port: 0. :) |
I've been running into this with elixir desktop, which currently integrates with cowboy specifically partly due to this. I'm fine if this might not be a responsibility for phoenix, but I'd also love to see a shared solution to the issue rather than a "figure it out on your own". Especially with the printed banner on startup it really feels strange that this would be the only place in phoenix knowing the actual port, with no other way to get to know it across all adapters. Edit: Seems like livebook also queries :ranch directly using what feels like phoenix integration details (the ranch references used). |
Building URLs for a server is hard. Setting aside issues of constructing URLs that account for load balancers, domain sockets, etc, even the simple case above is more nuanced than what José mentions, since the recent 'suspend/resume' patterns that @chrismccord et al have been working on will end up changing the port between resumptions. Ultimately it's really difficult to build a URL that has guaranteed meaning beyond the scope of a specific connection and as encoded already within a This isn't an isolated problem, either. There's currently a discussion going on over on uberauth that closely relates to this, and similar issues have come up a few times in Bandit's history. I do think that there's an opportunity to encapsulate all of this within the adapter layer, as e.g. LiveView having to know about ranch is a less than ideal encapsulation of the whole thing. I think we would want to:
Specifically with respect to this issue, it sounds like the appetite is to leave it alone for now. If we can find consensus on a broader approach that would solve this and issues such as the one on uberauth at the moment, I'm happy to drive implementation. WDYT? |
Making it a responsibility of the connection makes a lot of sense. For desktop apps, there aren’t proxies, so always accurate. We do have rewrite plugs for proxies too. |
This is what I feel is the missing piece – being able to initially connect to the dynamic port without depending on implementation details about the underlying servers. Any later route building based on an actual connection could be handled with plug/on_mount tooling to make sure Point 3. certainly sounds ambitious. I'm wondering if there's an in between solution, where the server implementation could inform phoenix when their bound interface has changed. |
We could augment the Plug.Adapter protocol to introduce a info function with metadata about the server but I'd say this is out of scope of Phoenix (or at least until it is tackled in Plug). :) Thanks @LostKobrakai! |
I'm planning on tackling a PR against Plug et al. for this in the next couple of weeks! |
This has been added to main. |
This is a naive implementation, as it doesn't yet support the adapter crashing and potentially getting assigned a new port. I'm not sure if cowboy or bandit for the matter crash all the way up to the endpoint in such a case, so phoenix could react to a new port or if there would need to be some other mechanism to make phoenix aware of the changed port.
That should still be better than leaving the
:0
, as it works right now, I think.Relates to #4186